feat(web): add /api/blame endpoint#1158
Conversation
Adds a new git helper (`getFileBlame`) that runs `git blame --porcelain` and parses the output into contiguous line ranges plus deduplicated commit metadata (hash, date, message, author, optional `previous` pointer for walking back through history). Exposes it as a new public REST endpoint `GET /api/blame`, mirroring the shape of `/api/source`. Registers OpenAPI schemas, updates the API Reference nav, and adds a `user.fetched_file_blame` audit event. API-only; the CodeMirror gutter UI is a follow-up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WalkthroughAdds a new public GET /api/blame endpoint with request/response schemas, server route handler, git-porcelain blame parser, audit logging integration, OpenAPI registration, and documentation updates. Changes
Sequence DiagramsequenceDiagram
participant Client
participant RouteHandler as Route Handler<br/>(/api/blame)
participant Validator as Parameter<br/>Validator
participant GitService as Git Service<br/>(getFileBlame)
participant GitCmd as Git Command<br/>(git blame)
participant Parser as Porcelain<br/>Parser
Client->>RouteHandler: GET /api/blame?repo=X&path=Y&ref=Z
RouteHandler->>Validator: Parse & validate query params
Validator-->>RouteHandler: Valid params or error
alt Invalid Parameters
RouteHandler-->>Client: 400 Error Response
else Valid Parameters
RouteHandler->>GitService: getFileBlame(repo, path, ref)
GitService->>GitService: Validate repo, path & ref
GitService->>GitService: Emit audit event (if user)
GitService->>GitCmd: Execute git blame --porcelain
GitCmd-->>GitService: Porcelain output or error
alt Command Failed
GitService-->>RouteHandler: ServiceError (fileNotFound / unresolvedGitRef / unexpectedError)
RouteHandler-->>Client: 404/500 Error Response
else Command Succeeded
GitService->>Parser: parsePorcelainBlame(output)
Parser-->>GitService: ranges + commits
GitService-->>RouteHandler: FileBlameResponse (200)
RouteHandler-->>Client: 200 JSON Response
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 6/8 reviews remaining, refill in 14 minutes and 57 seconds.Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/web/src/features/git/getFileBlameApi.ts`:
- Around line 170-180: The current catch block directly returns internal
git/parser text via unexpectedError(errorMessage), exposing sensitive internals;
instead capture the thrown error from git.raw in a local variable, log the full
error details server-side (e.g., logger.error with error and stack) and return a
generic API-facing error message (e.g., unexpectedError('An internal error
occurred while processing the blame request')); update both the git.raw catch
and the later parse-error paths (the branches that now call
unexpectedError(errorMessage)) to follow this pattern and keep
fileNotFound(filePath, repoName) and unresolvedGitRef(gitRef) behavior
unchanged.
- Around line 140-146: The call to getAuditService().createAudit({...}) in the
async handler should be awaited to avoid unhandled promise rejections and ensure
the audit completes; update the code in the function using withOptionalAuth to
change the fire-and-forget createAudit invocation to await
getAuditService().createAudit({...}) so the Promise<Audit|null> is properly
handled.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 88abb785-1145-462f-ae97-1f92a1109d56
📒 Files selected for processing (10)
CHANGELOG.mddocs/api-reference/sourcebot-public.openapi.jsondocs/docs.jsondocs/docs/configuration/audit-logs.mdxpackages/web/src/app/api/(server)/blame/route.tspackages/web/src/features/git/getFileBlameApi.tspackages/web/src/features/git/index.tspackages/web/src/features/git/schemas.tspackages/web/src/openapi/publicApiDocument.tspackages/web/src/openapi/publicApiSchemas.ts
There was a problem hiding this comment.
♻️ Duplicate comments (1)
packages/web/src/features/git/getFileBlameApi.ts (1)
170-180:⚠️ Potential issue | 🟠 MajorAvoid returning raw git/parser errors to public clients.
On Line 180 and Line 187, internal error text is surfaced directly through
unexpectedError(...). That leaks backend details; log server-side and return generic client-facing messages.Proposed hardening diff
import { getRepoPath } from '@sourcebot/shared'; +import { createLogger } from '@sourcebot/shared'; @@ type CommitMeta = FileBlameResponse['commits'][string]; +const logger = createLogger('getFileBlameApi'); @@ } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes('no such path') || errorMessage.includes('does not exist') || errorMessage.includes('fatal: path') || errorMessage.includes('no such file')) { return fileNotFound(filePath, repoName); } if (errorMessage.includes('unknown revision') || errorMessage.includes('bad revision') || errorMessage.includes('invalid object name')) { return unresolvedGitRef(gitRef); } - return unexpectedError(errorMessage); + logger.error({ error: errorMessage, repoName, filePath, gitRef }, 'git blame failed'); + return unexpectedError('Failed to compute file blame.'); } @@ } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); - return unexpectedError(`Failed to parse git blame output: ${errorMessage}`); + logger.error({ error: errorMessage, repoName, filePath, gitRef }, 'Failed to parse git blame output'); + return unexpectedError('Failed to parse file blame output.'); }Also applies to: 183-188
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/web/src/features/git/getFileBlameApi.ts` around lines 170 - 180, The handler around the git.raw(['blame'...]) call currently passes raw git/parser messages into unexpectedError(errorMessage) which leaks internals; instead, capture the error (error / errorMessage), write the full details to server logs (e.g., logger.error or processLogger.error) and call unexpectedError with a generic, client-safe message like "Unable to process file blame" or "Internal error while retrieving blame" so clients don't receive backend internals; keep existing branches that return fileNotFound(filePath, repoName) and unresolvedGitRef(gitRef) for known conditions, only replace the final return unexpectedError(errorMessage) with a logged internal error plus a generic unexpectedError call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@packages/web/src/features/git/getFileBlameApi.ts`:
- Around line 170-180: The handler around the git.raw(['blame'...]) call
currently passes raw git/parser messages into unexpectedError(errorMessage)
which leaks internals; instead, capture the error (error / errorMessage), write
the full details to server logs (e.g., logger.error or processLogger.error) and
call unexpectedError with a generic, client-safe message like "Unable to process
file blame" or "Internal error while retrieving blame" so clients don't receive
backend internals; keep existing branches that return fileNotFound(filePath,
repoName) and unresolvedGitRef(gitRef) for known conditions, only replace the
final return unexpectedError(errorMessage) with a logged internal error plus a
generic unexpectedError call.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e6e0609e-9bdf-49d3-8788-678cfcc8dc1e
📒 Files selected for processing (1)
packages/web/src/features/git/getFileBlameApi.ts
Summary
getFileBlamegit helper (packages/web/src/features/git/getFileBlameApi.ts) that runsgit blame --porcelainand parses the output into contiguous line ranges + deduplicated commit metadata.GET /api/blame, mirroring the shape and conventions of/api/source.PublicFileBlameRequest,PublicFileBlameResponse) and adds the endpoint to the API Reference nav.user.fetched_file_blameaudit event (mirroringuser.fetched_file_source).Response shape
Range-based (not per-line) so payload size scales with the number of contiguous attribution groups, not file length. Field naming aligns with the existing
commitSchema(hash,date,message).The
previouspointer is captured from porcelain so we can support a "blame at previous commit" reblame UX later.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Audit